﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using Microsoft.TeamFoundation.Client;
using System.Configuration;
using System.Web;
using System.Security.Principal;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Common;
using Microsoft.TeamFoundation.Server;
using System.Net;

namespace Scrum.SwimLanes.Models
{
    public interface IWorkItemRepository
    {
        ClientWorkItemList GetWorkItems(int sNo, string tp, WorkItemType workItemType);
        bool ChangeStatus(string teamProjectName, int sprintNumber, int workItemId, string newStatus);
    }

    public class WorkItemRepository : IWorkItemRepository
    {
        string _tfsUri = ConfigurationManager.AppSettings["TfsServerUri"].ToString();
        string _domain = ConfigurationManager.AppSettings["Domain"].ToString();
        string _tfsAdminUserName = ConfigurationManager.AppSettings["TfsAdminUserName"].ToString();
        string _tfsAdminPassword = ConfigurationManager.AppSettings["TfsAdminPassword"].ToString();
        string _workItemStoreCacheName = "WorkItemStoreCache";


        public WorkItemStore GetWorkItemStore(bool forceGet)
        {
            // First, check the cache
            WorkItemStore cachedStore = HttpRuntime.Cache.Get(_workItemStoreCacheName) as WorkItemStore;

            // If it's not in the cache, we need to read it from the repository
            if (forceGet || cachedStore == null)
            {
                // Get the repository data
                TeamFoundationServer tpc = GetTfsAdminServer();

                tpc.EnsureAuthenticated();
                cachedStore = tpc.GetService(typeof(WorkItemStore)) as WorkItemStore;                

                // Put this data into the cache for 15 minutes
                HttpRuntime.Cache.Insert(_workItemStoreCacheName, 
                    cachedStore, 
                    null, 
                    System.Web.Caching.Cache.NoAbsoluteExpiration,
                    new TimeSpan(0, 10, 0));
    
            }

            return cachedStore;
        }       

        private TeamFoundationServer GetTfsAdminServer()
        {
            NetworkCredential networkCredentials = new NetworkCredential(_tfsAdminUserName, _tfsAdminPassword, _domain);
            var tpc = new TeamFoundationServer(_tfsUri, networkCredentials as ICredentials);
            return tpc;
        }
        
        public ClientWorkItemList GetWorkItems(int sNo, string tp, WorkItemType workItemType)
        {
            var backlogItems = CreateWorkItemsCollection(sNo, tp, workItemType);
            var allWorkItems = new ClientWorkItemList();
            allWorkItems.NotDoneItems = backlogItems.Where(i => i.Status.ToString().Equals("Not Done")).ToList();
            allWorkItems.InProgressItems = backlogItems.Where(i => i.Status.ToString().Equals("In Progress")).ToList();
            allWorkItems.DoneItems = backlogItems.Where(i => i.Status.ToString().Equals("Done")).ToList();
            return allWorkItems;
        }

        /// <summary>
        /// The Following WIQL query works with SfTS (Scrum for Team System) WI template V3 only
        /// You can easily tweak it to suit your custom/agile or some other template
        /// </summary>
        /// <param name="sprintNumber"></param>
        /// <param name="teamProjectName"></param>
        /// <param name="workItemType"></param>
        /// <returns></returns>
        private List<ClientWorkItem> CreateWorkItemsCollection(int sprintNumber, string teamProjectName, WorkItemType workItemType)
        {
            WorkItemStore workItemStore = GetWorkItemStore(false);
            List<ClientWorkItem> workItems = new List<ClientWorkItem>();
            if (workItemStore != null)
            {
                var iterationPath = ConfigurationManager.AppSettings["IterationPathPrefix"].ToString() + sprintNumber.ToString();
                
                var queryString = "SELECT [System.Id], [System.Title], [System.Description], [System.State], [System.AreaPath], [System.AssignedTo], [System.TeamProject], [System.WorkItemType], [Conchango.TeamSystem.Scrum.EstimatedEffort]  FROM workitems WHERE ([System.TeamProject] = '" + 
                    teamProjectName + 
                    "' and [System.WorkItemType] = '" + 
                    GetWorkItemTypeString(workItemType) + 
                    "' and [System.IterationPath] under '" + 
                    iterationPath + "')";

                workItems = (from wi in workItemStore.Query(queryString).Cast<WorkItem>()
                              let id = Convert.ToInt32(wi.Fields["System.Id"].Value)
                              select new ClientWorkItem
                              {
                                  Id = id,
                                  Title = wi.Fields["System.Title"].Value != null ? wi.Fields["System.Title"].Value.ToString() : "No Title Specified",
                                  Description = wi.Fields["System.Description"].Value != null ? wi.Fields["System.Description"].Value.ToString() : "No Description Specified",
                                  Status = wi.Fields["System.State"].Value.ToString(),
                                  AssignedTo = wi.Fields["System.AssignedTo"].Value != null ? wi.Fields["System.AssignedTo"].Value.ToString() : "Not Assigned",
                                  AreaPath = wi.Fields["System.AreaPath"].Value != null ? wi.Fields["System.AreaPath"].Value.ToString() : "No AreaPath",
                                  SprintNumber = sprintNumber,
                                  TeamProjectName = wi.Fields["System.TeamProject"].Value.ToString(),
                                  WorkItemType = GetWorkItemType(wi.Fields["System.WorkItemType"].Value.ToString()),
                                  EstimatedEffort = wi.Fields["Conchango.TeamSystem.Scrum.EstimatedEffort"].Value != null ? wi.Fields["Conchango.TeamSystem.Scrum.EstimatedEffort"].Value.ToString() : "None",
                                  WorkRemaining = wi.Fields["Conchango.TeamSystem.Scrum.WorkRemaining"].Value != null ? wi.Fields["Conchango.TeamSystem.Scrum.WorkRemaining"].Value.ToString() : "None",
                                  RelatedWorkItems = wi.RelatedLinkCount
                              }).ToList();
            }
            return workItems;
        }

        private WorkItemCounts GetWorkItemCounts(WorkItem workItem, WorkItemType workItemType)
        {
            var notDoneCount = 0;
            var inProgressCount = 0;
            var doneCount = 0;
            if (workItemType == WorkItemType.ProductBacklogItem)
            {
                foreach (var item in workItem.Links)
                {
                    if (item is RelatedLink)
                    {
                        var workItemId = (item as RelatedLink).RelatedWorkItemId;
                        var relatedWorkItem = GetWorkItem(workItemId);
                        if (relatedWorkItem.State.Equals("Not Done")) notDoneCount++;
                        if (relatedWorkItem.State.Equals("In Progress")) inProgressCount++;
                        if (relatedWorkItem.State.Equals("Done")) doneCount++;
                    }
                }
            }
            var wiCounts = new WorkItemCounts();
            wiCounts.NotDoneItemCount = notDoneCount;
            wiCounts.InProgressItemCount = inProgressCount;
            wiCounts.DoneItemCount = doneCount;

            return wiCounts;
        }

        private WorkItem GetWorkItem(int workItemId)
        {
            WorkItemStore workItemStore = GetWorkItemStore(false);
            var workItem = workItemStore.GetWorkItem(workItemId);
            return workItem;
        }

        private string GetWorkItemTypeString(WorkItemType workItemType)
        {
            string workItemTypeString = default(string);
            switch (workItemType)
            {
                case WorkItemType.ProductBacklogItem:
                    workItemTypeString = "Product Backlog Item";
                    break;
                case WorkItemType.SprintBacklogItem:
                    workItemTypeString = "Sprint Backlog Item";
                    break;
                default:
                    break;
            }
            return workItemTypeString;
        }

        private Models.WorkItemType GetWorkItemType(string workItemType)
        {
            if (workItemType.Equals("Product Backlog Item")) return Models.WorkItemType.ProductBacklogItem;
            if (workItemType.Equals("Sprint Backlog Item")) return Models.WorkItemType.SprintBacklogItem;

            return Models.WorkItemType.SprintBacklogItem;
        }

        public bool ChangeStatus(string teamProjectName,
            int sprintNumber,
            int workItemId,
            string newStatus)
        {
            var accountName = HttpContext.Current.User.Identity.Name.Replace(_domain + "\\\\", string.Empty);
            if (IsValidWriteUser(accountName, teamProjectName))
            {
                if (newStatus.Equals("Deleted") || newStatus.Equals("Done") || newStatus.Equals("In Progress"))
                {
                    var workItemToChange = GetWorkItem(workItemId);
                    workItemToChange.Open();

                    // Quotes around the newStatus value are not allowed, when being sent from client
                    // added for safety, currently the client does not send any surroudning quotes, yey!
                    newStatus = newStatus.Trim(new char[] { '"' });
                    workItemToChange.State = newStatus;

                    // You can't change status from "In Progress" to "Not Done", so we first have to set it to "Deleted" and then to "Not Done"
                    if (newStatus.Equals("Deleted", StringComparison.OrdinalIgnoreCase))
                    {
                        workItemToChange.Save();
                        workItemToChange.State = "Not Done";
                    }
                    
                    workItemToChange.Save();
                    return true;
                }
            }
            return false;
        }

        internal bool IsValidWriteUser(string accountName, string teamProjectName)
        {
            return GetUsers(teamProjectName).Contains(accountName);
        }

        /// <summary>
        /// May be there is a better way to get users for a given team project, but for now this is all I got
        /// </summary>
        /// <param name="project"></param>
        /// <returns></returns>
        public List<string> GetUsers(string project)
        {
            List<string> users = new List<string>();
            IGroupSecurityService securityService = (IGroupSecurityService)GetTfsAdminServer().GetService(typeof(IGroupSecurityService));

            Identity[] projectGroups = securityService.ListApplicationGroups(GetWorkItemStore(false).Projects[project].Uri.AbsoluteUri)
                .Where(i => i.DisplayName.Equals("Project Administrators", StringComparison.OrdinalIgnoreCase) ||
                    i.DisplayName.Equals("Contributors", StringComparison.OrdinalIgnoreCase)).ToArray();


            foreach (Identity projectGroup in projectGroups)
            {
                Identity[] groupMembers = securityService.ReadIdentities (SearchFactor.Sid, new string[] { projectGroup.Sid },
                                 QueryMembership.Expanded);

                foreach (Identity member in groupMembers)
                {
                    if (member.Members != null)
                    {
                        foreach (string memberSid in member.Members)
                        {
                            Identity memberInfo = securityService.ReadIdentity (SearchFactor.Sid, memberSid, QueryMembership.None);
                            users.Add(memberInfo.AccountName);
                        }
                    }
                }
            }
            return users;
        }
    }
}